package com.px.project.module.it.service.impl;

import java.math.BigDecimal;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import com.px.common.config.security.LoginContextHolder;
import com.px.common.constant.SymbolConstant;
import com.px.common.dto.BaseQueryDto;
import com.px.common.enums.ApproveStatusEnum;
import com.px.common.service.ActivitiService;
import com.px.project.module.ht.constant.FileTypeConstant;
import com.px.project.module.it.dto.ContrastMessageQueryDto;
import com.px.project.module.it.dto.EngineeringJobAssignDto;
import com.px.project.module.it.dto.EquipmentEquipmentAssignDto;
import com.px.project.module.it.dto.ItApproveDto;
import com.px.project.module.it.dto.MaterialAssignDto;
import com.px.project.module.it.dto.ProgressMessageQueryDto;
import com.px.project.module.it.dto.TempPlanDto;
import com.px.project.module.it.dto.TempPlanQueryDto;
import com.px.project.module.it.entity.TempPlanDao;
import com.px.project.module.it.entity.TempPlanDetailDao;
import com.px.project.module.it.enums.AssignSourceTypeEnum;
import com.px.project.module.it.enums.ItProcessEnum;
import com.px.project.module.it.mapper.ITempPlanMapper;
import com.px.project.module.it.service.ICommonPlanService;
import com.px.project.module.it.service.IEngineeringJobAssignService;
import com.px.project.module.it.service.IEquipmentEquipmentAssignService;
import com.px.project.module.it.service.IMaterialAssignService;
import com.px.project.module.it.service.ITempPlanDetailService;
import com.px.project.module.it.service.ITempPlanService;
import com.px.project.module.it.service.InspectCheckService;
import com.px.project.module.it.vo.ContrastMessagePlanVo;
import com.px.project.module.it.vo.EngineeringJobAssignVo;
import com.px.project.module.it.vo.EquipmentEquipmentAssignVo;
import com.px.project.module.it.vo.MaterialAssignVo;
import com.px.project.module.it.vo.ProgressMessagePlanVo;
import com.px.project.module.it.vo.TempPlanVo;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.thread.GlobalThreadPool;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * 智能排产》》施工计划》》修改计划业务逻辑实现
 *
 * @author 品讯科技
 * @date 2024-08
 */
@Slf4j
@Service(value = "itTempPlanServiceImpl")
@RequiredArgsConstructor
public class TempPlanServiceImpl extends ServiceImpl<ITempPlanMapper, TempPlanDao> implements ITempPlanService {

    private static final Integer KM_TO_M = 1000;

    private static final Integer STATE_NORMAL = 1;

    private static final Integer STATE_POSTPONE = 0;

    private static final Integer SCALE = 2;

    private static final String DEFAULT_SCHEDULING = "0.00";

    private static final String COEFFICIENT = "100";

    private static final Integer DELETED_ON = 0;

    private final ITempPlanMapper tempPlanMapper;

    private final ActivitiService activitiService;

    private final ICommonPlanService commonService;

    private final IMaterialAssignService materialAssignService;

    private final IEngineeringJobAssignService engineeringJobAssignService;

    private final IEquipmentEquipmentAssignService equipmentEquipmentAssignService;

    private final ITempPlanDetailService tempPlanDetailService;

    private final InspectCheckService inspectCheckService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean saveOfBatch(TempPlanDto tempPlan) {
        TempPlanDao tempPlanDao = BeanUtil.copyProperties(tempPlan, TempPlanDao.class);
        tempPlanDao.setApproveStatus(ApproveStatusEnum.APPROVING.getCode()).setApplyUserId(LoginContextHolder.me().getId()).setApplyTime(new Date());
        if (ObjectUtil.isNotEmpty(tempPlan.getCarbonCopyUserIds())) {
            String carbonCopyUserId = tempPlan.getCarbonCopyUserIds().stream().map(Object::toString).collect(Collectors.joining(StrPool.COMMA));
            tempPlanDao.setCarbonCopyUserId(carbonCopyUserId);
        }
        super.save(tempPlanDao);
        List<TempPlanDetailDao> tempPlanDetails = tempPlan.getTempPlanDetails().stream().map(monthPlanDetailDto -> {
            TempPlanDetailDao tempPlanDetailDao = BeanUtil.copyProperties(monthPlanDetailDto, TempPlanDetailDao.class);
            tempPlanDetailDao.setTempPlanId(tempPlanDao.getId());
            if (ObjectUtil.isNotEmpty(monthPlanDetailDto.getSafetySupervisorUserIds())) {
                String safetySupervisorUserId =
                    monthPlanDetailDto.getSafetySupervisorUserIds().stream().map(Object::toString).collect(Collectors.joining(StrPool.COMMA));
                tempPlanDetailDao.setSafetySupervisorUserId(safetySupervisorUserId);
            }
            if (ObjectUtil.isNotEmpty(monthPlanDetailDto.getResponsibleUserIds())) {
                String responsibleUserId =
                    monthPlanDetailDto.getResponsibleUserIds().stream().map(Object::toString).collect(Collectors.joining(StrPool.COMMA));
                tempPlanDetailDao.setResponsibleUserId(responsibleUserId);
            }
            return tempPlanDetailDao;
        }).collect(Collectors.toList());
        this.tempPlanDetailService.saveBatch(tempPlanDetails);
        /* 启动工作流程 */
        ProcessInstance processInstance =
            this.activitiService.startProcess(ItProcessEnum.IT_TEMP_PLAN.getCodeName(), String.valueOf(tempPlanDao.getId()), new HashMap<>());
        String nextApproveNode = this.activitiService.getFlowNexApproveNode(processInstance.getProcessInstanceId());
        UpdateWrapper<TempPlanDao> updateWrapper = new UpdateWrapper<>();
        updateWrapper.lambda().set(TempPlanDao::getUpdateTime, new Date()).set(TempPlanDao::getUpdateBy, LoginContextHolder.me().getId())
            .set(TempPlanDao::getProcessInstanceId, processInstance.getProcessInstanceId()).set(TempPlanDao::getNextApproveNode, nextApproveNode)
            .eq(TempPlanDao::getId, tempPlanDao.getId()).eq(TempPlanDao::getDeleted, DELETED_ON);
        super.update(updateWrapper);
        return Boolean.TRUE;
    }

    @Override
    public Page<TempPlanVo> findPages(BaseQueryDto<TempPlanDao> baseQuery, TempPlanQueryDto tempPlanQuery) {
        Page<TempPlanVo> pages = this.tempPlanMapper.selectPages(baseQuery.toPage(), tempPlanQuery);
        List<String> processInstanceIds = pages.getRecords().stream().map(TempPlanVo::getProcessInstanceId).collect(Collectors.toList());
        List<Task> tasks = this.activitiService.findBatchTasks(processInstanceIds);
        String userName = LoginContextHolder.me().getUsername();
        Stream<CompletableFuture<Void>> completableFutureStream = pages.getRecords().stream().map(tempPlan -> CompletableFuture.runAsync(() -> {
            this.commonService.writeBackPlan(tasks, tempPlan, AssignSourceTypeEnum.IT_TEMP_PLAN.getCode(), userName);
            this.findAssignInfo(tempPlan);
            this.commonService.getFiles(tempPlan, FileTypeConstant.IT_TEMP_PLAN);
        }, GlobalThreadPool.getExecutor()));
        CompletableFuture.allOf(completableFutureStream.toArray(CompletableFuture[]::new)).join();
        this.commonService.writeUserName(pages);
        List<TempPlanVo> tempPlans = pages.getRecords().stream().sorted(Comparator.comparing(TempPlanVo::getCreateTime, Comparator.reverseOrder()))
            .collect(Collectors.toList());
        pages.setRecords(tempPlans);
        return pages;
    }

    private void findAssignInfo(TempPlanVo tempPlan) {
        CompletableFuture<List<MaterialAssignVo>> materialAssignFuture = CompletableFuture
            .supplyAsync(() -> this.materialAssignService.findOfAllByObjectIdAndSourceType(tempPlan.getObjectId(),
                AssignSourceTypeEnum.IT_TEMP_PLAN.getCode()), GlobalThreadPool.getExecutor())
            .whenComplete((data, throwable) -> tempPlan.setMaterialAssigns(data));
        materialAssignFuture.join();
        CompletableFuture<List<EngineeringJobAssignVo>> engineeringJobAssignFuture = CompletableFuture
            .supplyAsync(() -> this.engineeringJobAssignService.findOfAllByObjectIdAndSourceType(tempPlan.getObjectId(),
                AssignSourceTypeEnum.IT_TEMP_PLAN.getCode()), GlobalThreadPool.getExecutor())
            .whenComplete((data, throwable) -> tempPlan.setEngineeringJobAssigns(data));
        engineeringJobAssignFuture.join();
        CompletableFuture<List<EquipmentEquipmentAssignVo>> equipmentEquipmentAssignFuture = CompletableFuture
            .supplyAsync(() -> this.equipmentEquipmentAssignService.findOfAllByObjectIdAndSourceType(tempPlan.getObjectId(),
                AssignSourceTypeEnum.IT_TEMP_PLAN.getCode()), GlobalThreadPool.getExecutor())
            .whenComplete((data, throwable) -> tempPlan.setEquipmentEquipmentAssigns(data));
        equipmentEquipmentAssignFuture.join();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean approveProcess(ItApproveDto itApprove) {
        this.activitiService.isFinished(itApprove.getProcessInstanceId());
        this.activitiService.handleProcess(itApprove.getProcessInstanceId(), itApprove.getApproveOperation(), itApprove.getComment(),
            LoginContextHolder.me().getUsername());
        /* 更新下一个审批节点内容 */
        UpdateWrapper<TempPlanDao> updateWrapper = new UpdateWrapper<>();
        updateWrapper.lambda().set(TempPlanDao::getUpdateTime, new Date()).set(TempPlanDao::getUpdateBy, LoginContextHolder.me().getId())
            .set(TempPlanDao::getNextApproveNode, this.activitiService.getFlowNexApproveNode(itApprove.getProcessInstanceId()))
            .eq(TempPlanDao::getProcessInstanceId, itApprove.getProcessInstanceId()).eq(TempPlanDao::getDeleted, DELETED_ON);
        super.update(updateWrapper);
        return Boolean.TRUE;
    }

    @Override
    public TempPlanVo findInfoOne(Long objectId) {
        TempPlanDao tempPlanDao = this.tempPlanMapper.selectByObjectId(objectId);
        TempPlanVo tempPlan = BeanUtil.copyProperties(tempPlanDao, TempPlanVo.class);
        String userName = LoginContextHolder.me().getUsername();
        this.commonService.writeBackPlan(tempPlan, userName);
        this.commonService.writeUserNameOfOne(tempPlan);
        this.findAssignInfo(tempPlan);
        return tempPlan;
    }

    @Override
    public List<TempPlanVo> listOfAllByProjectManagementId(Long projectManagementId) {
        QueryWrapper<TempPlanDao> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(TempPlanDao::getProjectManagementId, projectManagementId).orderByDesc(TempPlanDao::getCreateTime);
        List<TempPlanDao> tempPlans = super.list(queryWrapper);
        return BeanUtil.copyToList(tempPlans, TempPlanVo.class);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean saveOfBatchEngineeringJobAssign(List<EngineeringJobAssignDto> engineeringJobAssigns) {
        engineeringJobAssigns = engineeringJobAssigns.stream()
            .map(engineeringJobAssignDto -> engineeringJobAssignDto.setSourceType(AssignSourceTypeEnum.IT_TEMP_PLAN.getCode()))
            .collect(Collectors.toList());
        this.engineeringJobAssignService.saveOfBatch(engineeringJobAssigns);
        return Boolean.TRUE;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateOfBatchEngineeringJobAssign(List<EngineeringJobAssignDto> engineeringJobAssigns) {
        engineeringJobAssigns = engineeringJobAssigns.stream()
            .map(engineeringJobAssignDto -> engineeringJobAssignDto.setSourceType(AssignSourceTypeEnum.IT_TEMP_PLAN.getCode()))
            .collect(Collectors.toList());
        this.engineeringJobAssignService.updateOfBatch(engineeringJobAssigns);
        return Boolean.TRUE;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean saveOfBatchEquipmentEquipmentAssign(List<EquipmentEquipmentAssignDto> equipmentEquipmentAssigns) {
        equipmentEquipmentAssigns = equipmentEquipmentAssigns.stream()
            .map(equipmentEquipmentAssignDto -> equipmentEquipmentAssignDto.setSourceType(AssignSourceTypeEnum.IT_TEMP_PLAN.getCode()))
            .collect(Collectors.toList());
        this.equipmentEquipmentAssignService.saveOfBatch(equipmentEquipmentAssigns);
        return Boolean.TRUE;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateOfBatchEquipmentEquipmentAssign(List<EquipmentEquipmentAssignDto> equipmentEquipmentAssigns) {
        equipmentEquipmentAssigns = equipmentEquipmentAssigns.stream()
            .map(equipmentEquipmentAssignDto -> equipmentEquipmentAssignDto.setSourceType(AssignSourceTypeEnum.IT_TEMP_PLAN.getCode()))
            .collect(Collectors.toList());
        this.equipmentEquipmentAssignService.updateOfBatch(equipmentEquipmentAssigns);
        return Boolean.TRUE;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean saveOfBatchMaterialAssign(List<MaterialAssignDto> materialAssigns) {
        materialAssigns = materialAssigns.stream()
            .map(materialAssignDto -> materialAssignDto.setSourceType(AssignSourceTypeEnum.IT_TEMP_PLAN.getCode())).collect(Collectors.toList());
        this.materialAssignService.saveOfBatch(materialAssigns);
        return Boolean.TRUE;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateOfBatchMaterialAssign(List<MaterialAssignDto> materialAssigns) {
        materialAssigns = materialAssigns.stream()
            .map(materialAssignDto -> materialAssignDto.setSourceType(AssignSourceTypeEnum.IT_TEMP_PLAN.getCode())).collect(Collectors.toList());
        this.materialAssignService.updateOfBatch(materialAssigns);
        return Boolean.TRUE;
    }

    @Override
    public Page<ProgressMessagePlanVo> findPagesOfProgress(BaseQueryDto<?> baseQuery, ProgressMessageQueryDto progressMessageQuery) {
        Page<ProgressMessagePlanVo> pages = this.tempPlanMapper.selectPagesOfProgress(baseQuery.toPage(), progressMessageQuery);
        Stream<CompletableFuture<Void>> completableFutureStream = pages.getRecords().stream().map(progressMessagePlan -> CompletableFuture
            .runAsync(() -> this.setProgressMessagePlan(progressMessagePlan), GlobalThreadPool.getExecutor()));
        CompletableFuture.allOf(completableFutureStream.toArray(CompletableFuture[]::new)).join();
        this.commonService.writeUserName(pages);
        return pages;
    }

    private void setProgressMessagePlan(ProgressMessagePlanVo progressMessagePlan) {
        int totalDistance = (progressMessagePlan.getMileageEndKm() * KM_TO_M + progressMessagePlan.getMileageEndM())
            - (progressMessagePlan.getMileageStartKm() * KM_TO_M + progressMessagePlan.getMileageStartM());
        progressMessagePlan.setTotalDistance(Math.abs(totalDistance));
        progressMessagePlan.setSourceType(AssignSourceTypeEnum.IT_TEMP_PLAN.getCode());
        progressMessagePlan.setSourceTypeName(AssignSourceTypeEnum.IT_TEMP_PLAN.getName());
        if (new Date().before(progressMessagePlan.getPlanCycleEnd())) {
            progressMessagePlan.setProgressState(STATE_NORMAL);
        } else {
            progressMessagePlan.setProgressState(STATE_POSTPONE);
        }
        Integer completeDistance =
            this.inspectCheckService.getSumMileage(AssignSourceTypeEnum.IT_TEMP_PLAN.getCode(), progressMessagePlan.getObjectId());
        progressMessagePlan.setCompleteDistance(completeDistance);
        try {
            progressMessagePlan.setWorkScheduling(
                (NumberUtil.div(progressMessagePlan.getCompleteDistance(), progressMessagePlan.getTotalDistance(), SCALE.intValue())
                    .multiply(new BigDecimal(COEFFICIENT))) + SymbolConstant.PERCENT);
        } catch (ArithmeticException e) {
            progressMessagePlan.setWorkScheduling(DEFAULT_SCHEDULING + SymbolConstant.PERCENT);
        }
    }

    @Override
    public List<ProgressMessagePlanVo> findListOfProgress(ProgressMessageQueryDto progressMessageQuery) {
        List<ProgressMessagePlanVo> progressMessagePlans = this.tempPlanMapper.selectListOfProgress(progressMessageQuery);
        Stream<CompletableFuture<Void>> completableFutureStream = progressMessagePlans.stream().map(progressMessagePlan -> CompletableFuture
            .runAsync(() -> this.setProgressMessagePlan(progressMessagePlan), GlobalThreadPool.getExecutor()));
        CompletableFuture.allOf(completableFutureStream.toArray(CompletableFuture[]::new)).join();
        this.commonService.writeUserName(progressMessagePlans);
        return progressMessagePlans;
    }

    @Override
    public Page<ContrastMessagePlanVo> findPagesOfContrast(BaseQueryDto<?> baseQuery, ContrastMessageQueryDto contrastMessageQuery) {
        Page<ContrastMessagePlanVo> pages = this.tempPlanMapper.selectPagesOfContrast(baseQuery.toPage(), contrastMessageQuery);
        Stream<CompletableFuture<Void>> completableFutureStream = pages.getRecords().stream().map(contrastMessagePlan -> CompletableFuture
            .runAsync(() -> this.setContrastMessagePlan(contrastMessagePlan), GlobalThreadPool.getExecutor()));
        CompletableFuture.allOf(completableFutureStream.toArray(CompletableFuture[]::new)).join();
        this.commonService.writeUserName(pages);
        return pages;
    }

    private void setContrastMessagePlan(ContrastMessagePlanVo contrastMessagePlan) {
        contrastMessagePlan.setSourceType(AssignSourceTypeEnum.IT_TEMP_PLAN.getCode());
        contrastMessagePlan.setSourceTypeName(AssignSourceTypeEnum.IT_TEMP_PLAN.getName());
        List<EngineeringJobAssignVo> engineeringJobAssigns = this.engineeringJobAssignService
            .findOfAllByObjectIdAndSourceType(contrastMessagePlan.getObjectId(), AssignSourceTypeEnum.IT_TEMP_PLAN.getCode());
        contrastMessagePlan.setPlanNumber(engineeringJobAssigns.stream().mapToInt(EngineeringJobAssignVo::getNumber).sum());
        if (new Date().before(contrastMessagePlan.getPlanCycleEnd())) {
            contrastMessagePlan.setContrastState(STATE_NORMAL);
        } else {
            contrastMessagePlan.setContrastState(STATE_POSTPONE);
        }
        // TODO 实际人数
        contrastMessagePlan.setActualNumber(0);
        try {
            contrastMessagePlan
                .setWorkScheduling((NumberUtil.div(contrastMessagePlan.getActualNumber(), contrastMessagePlan.getPlanNumber(), SCALE.intValue())
                    .multiply(new BigDecimal(COEFFICIENT))) + SymbolConstant.PERCENT);
        } catch (ArithmeticException e) {
            contrastMessagePlan.setWorkScheduling(DEFAULT_SCHEDULING + SymbolConstant.PERCENT);
        }
    }

    @Override
    public List<ContrastMessagePlanVo> findListOfContrast(ContrastMessageQueryDto contrastMessageQuery) {
        List<ContrastMessagePlanVo> contrastMessagePlans = this.tempPlanMapper.selectListOfContrast(contrastMessageQuery);
        Stream<CompletableFuture<Void>> completableFutureStream = contrastMessagePlans.stream().map(contrastMessagePlan -> CompletableFuture
            .runAsync(() -> this.setContrastMessagePlan(contrastMessagePlan), GlobalThreadPool.getExecutor()));
        CompletableFuture.allOf(completableFutureStream.toArray(CompletableFuture[]::new)).join();
        this.commonService.writeUserName(contrastMessagePlans);
        return contrastMessagePlans;
    }
}